-
-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
329: Retain floating point precision when converting floats #331
329: Retain floating point precision when converting floats #331
Conversation
The cast of |
I agree, I originally wanted to use
Ah, sorry, wasn't aware. I'll apply for a membership. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside from the cast which could be improved a bit, please @daniel-shuy join the JCP as Associate member if you can. Ask @andi-huber who also is an Associate member if you need help. Will keep this open for now, it does not sound ultra-critical for the upcoming 2.1.2 release of Indriya but happy to merge once the JCP membership is sorted (hoping there are no merge conflicts;-)
Float#doubleValue() does not preserve floating point precision
88925bd
to
c0a5e9a
Compare
I've applied the suggestions and added tests |
@daniel-shuy How is your JCP membership going? |
@keilw I discussed with my employer and they wanted to join the JCP as an organization, so I was waiting for it to be approved before I can register under my employer. The organization membership has been approved, now I just need to register as an individual and request member association. Sorry, I've been putting it off because there hasn't been any activity in the discussion since (#332). |
@keilw If I am joining the JCP under an organization, do I have to ask my employer to apply for membership to the JSR or can I do that individually? |
@daniel-shuy No, Associate Membership works without asking your employer, see https://jcp.org/en/participation/membership the two bottom options. There is currently no cost for either, but if your employer is not interested to get involved then Associate sounds best and we have plenty of Associate members who contribute the same or sometimes even more than other Full members. |
@keilw my employer wants to get involved, as I am contributing back to libraries that are used by the company. I've joined the JCP under my employer, and I've submitted a nomination to join the JSR, thanks! |
Please understand, I don't see a bug with the current implementation. By all means, please, if you find an issue other than that, which is subject to the discussion (#332), of course I'll have a closer look. |
@andi-huber yes, we've established its not a bug, but a potential improvement to make it less confusing for users of the library. If not, I propose adding a note to warn users of the library that converting floats between different units can result in additional "noise" (while it is technically more accurate, the end user may not know that, and to them it is noise). |
@daniel-shuy Float is usually less precise see https://stackoverflow.com/questions/27598078 I don't think we need to put any note for what is common knowledge and reason. About the PR @andi-huber please have a look if any of the proposed changes would do harm, e.g. creating |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR was intended under the premise of cutting off float's at their decimal representation accuracy limit. If that were our goal, I'd consider this contribution at its most parts valid.
But unfortunately the suggested changes break with the current scheme that is to properly extend floats to double before converting them to any higher precision container.
} | ||
if(number instanceof Float) { | ||
return RationalNumber.of(number.doubleValue()).reciprocal(); | ||
return RationalNumber.of(number.floatValue()).reciprocal(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not introduce a RationalNumber.of(float) for the reasons discussed in (#332).
@@ -615,7 +615,7 @@ private BigDecimal toBigDecimal(Number number) { | |||
return BigDecimal.valueOf(number.longValue()); | |||
} | |||
if(number instanceof Double || number instanceof Float) { | |||
return BigDecimal.valueOf(number.doubleValue()); | |||
return new BigDecimal(number.toString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather not convert floats to String and then to BigDecimal if the general scheme (as up for discussion in #332) is to first properly extend floats to double before converting to BigDecimal. If we agree on this scheme we should be consistent with it throughout the entire implementation.
@@ -668,7 +668,7 @@ private Number addWideAndNarrow( | |||
} | |||
|
|||
if(narrow instanceof Double || narrow instanceof Float) { | |||
return ((BigDecimal) wide).add(BigDecimal.valueOf(narrow.doubleValue()), Calculus.MATH_CONTEXT); | |||
return ((BigDecimal) wide).add(new BigDecimal(narrow.toString()), Calculus.MATH_CONTEXT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dito: consistency
return BigDecimal.valueOf(wide.doubleValue()) | ||
.add(BigDecimal.valueOf(narrow.doubleValue())); | ||
return new BigDecimal(wide.toString()) | ||
.add(new BigDecimal(narrow.toString())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dito: consistency
* | ||
* @param number | ||
*/ | ||
public static RationalNumber of(float number) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, breaks the general scheme of first extending floats to double before converting them to BigDecimal or Rational.
} | ||
|
||
if(narrow instanceof RationalNumber) { | ||
//TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber | ||
return BigDecimal.valueOf(wide.doubleValue()) | ||
return new BigDecimal(wide.toString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point wide is potentially a float, so don't break consistency.
.multiply(((RationalNumber) narrow).bigDecimalValue()); | ||
} | ||
|
||
if(narrow instanceof BigInteger) { | ||
return BigDecimal.valueOf(wide.doubleValue()) | ||
return new BigDecimal(wide.toString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dito: consistency
.multiply(new BigDecimal((BigInteger) narrow)); | ||
} | ||
|
||
// at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte} | ||
return BigDecimal.valueOf(wide.doubleValue()) | ||
.multiply(BigDecimal.valueOf(narrow.longValue())); | ||
return new BigDecimal(wide.toString()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At this point wide is potentially a float, so don't break consistency.
@@ -821,7 +826,7 @@ private int compareWideVsNarrow( | |||
} | |||
|
|||
if(narrow instanceof Double || narrow instanceof Float) { | |||
return ((BigDecimal) wide).compareTo(BigDecimal.valueOf(narrow.doubleValue())); | |||
return ((BigDecimal) wide).compareTo(new BigDecimal(narrow.toString())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dito: consistency
@@ -837,22 +842,23 @@ private int compareWideVsNarrow( | |||
// at this point we know, that wide is one of {Double, Float} | |||
|
|||
if(narrow instanceof Double || narrow instanceof Float) { | |||
return Double.compare(wide.doubleValue(), narrow.doubleValue()); | |||
return new BigDecimal(wide.toString()) | |||
.compareTo(new BigDecimal(narrow.toString())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dito: consistency
If there is enough momentum, we surely can think of providing different |
Hi @daniel-shuy - on a personal note: These technical discussions are sometimes time consuming, exhausting and have a tendency toward frustration, especially if precious spare time is going into it. In that context, I want to be clear, that you are very welcome as a new contributor. |
@andi-huber thanks for taking the time to review the PR. After the discussion, I'm actually in agreement to not change the current behavior, due to the corner cases. I was actually trying to push for adding a note to warn users of the behavior, but as @keilw pointed out above, if a user of the library wants accuracy, then they shouldn't be using I'm happy to drop this issue, but I'd still be glad to join the JSR, as I have other improvements I would like to contribute back. |
@daniel-shuy Thanks a lot for the update and understanding. We're happy to accept your invitation to the JSR and EG as a contributor anticipating further activity or improvement. For the number systems @andi-huber mentioned, please also see this discusison item #328. It is not fully fleshed out with comments to actually vote on, but the idea is to use this new GH feature and offer 2 or 3 candidates like Commons Number or Decimal4J and implement either one or multiple plugins to replace the built-in |
Fixes #329
DefaultNumberSystem
usesBigDecimal
to preserve floating point precision for double arithmetic, butFloat#doubleValue()
does not preserve floating point precision, leading to precision gain for float arithmetic.RationalNumber#of(float)
to replaceRationalNumber.of(float.doubleValue())
Float
toBigDecimal
usingnew BigDecimal(float.toString())
instead ofBigDecimal.valueOf(float.doubleValue())
(to preserve precision)This change is